#!/usr/bin/env python2
#-*- coding: utf-8 -*-
"""
Created on 20101114
Author: zkz
"""

import commands
import errno
import fcntl
import logging
import logging.handlers
import inspect
import os
import re
import subprocess
import sys
import math
import types
import web
import threading
import eventlet
import functools
from pprint import pprint

from contextlib import contextmanager, closing
from Ump.common.exception import LichFault, AuthenticationFailed, LichLiscenFault


LOG = logging.getLogger('Ump.common.utils')


def current_dir():
    return os.path.dirname(__file__)

def content_replace(file_path, tobe_prepared, prepared_by):
    c = open(file_path).read()
    c = re.sub(tobe_prepared, prepared_by, c)
    open(file_path, 'wb').write(c)

def ensure_dir(dir_f):
    """如果没有创建目录"""
    dir_ = os.path.dirname(dir_f)
    if not os.path.isdir(dir_):
        LOG.info("*** make dir: %s" % dir_)
        os.makedirs(dir_)

def login_required(func):
    def new_func(*args, **argvkw):
        session = web.ctx.session
        url =  web.ctx.home
        urls = url.split(':')
        port = urls[-1].split('/')[0]
        home = urls[:-1]
        home.append(port)
        res = ':'.join(home)
        web.ctx.home = res
        if 'is_login' in session.keys() and session.is_login == 1:
            return func(*args, **argvkw)
        else:
            raise web.seeother('/login')
    return new_func

def lock_required(filename=None):
    '''instance_id_index是instance_id在参数的中位置，从0开始'''
    def new_func(func):
        def check(*args, **kwargs):
            with lock_file(filename):
                return func(*args, **kwargs)
        return check
    return new_func

def local_exec(cmd):
    try:
        p = subprocess.Popen(args=cmd,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             close_fds=True,
                             shell=True)
        p.wait()
        out = p.stdout.read()
        err = p.stderr.read()
        return out, err
    except:
        raise

def str2list(_str):
    '''str以空格为分割符号'''
    sstr = _str.strip()
    if sstr == '':
        return []
    else:
        return sstr.split(' ')

def str_list(_str,sign):
    '''str以sign为分割符号'''
    sstr = _str.strip()[:-1]
    if sstr == '':
        return []
    else:
        
        return sstr.split(sign)

def flat_dict_helper(prepand,d):
    if len(prepand) > 0:
        prepand = prepand + "_"

    for k in d:
        i=d[k]
        if type(i).__name__=='dict':
            r = flat_dict_helper(prepand+k,i)
            for j in r:
                yield j
        else:
            yield (prepand+k,i)

def flat_dict(d):
    '''
    >>d={'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
    >>print(flat_dict(d))
    >> {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
    '''
    return dict(flat_dict_helper("", d))

def reraise(exc_info=(None, None, None), errorMsg=''):
    """Re-raise the latest exception given by exc_info tuple (as returned by
    sys.exc_info()) with additional errorMsg text.
    Exceptions with non-standard constructors get re-raised as derived
    exceptions, with recorded original error message and original traceback.
    Parameters:
        exc_info: (<exception class>, <exception instance>, <traceback object>)
            tuple
        errorMsg: error message text to add to the exception error message

        ...     try:
        ...             b = str(s)
        ...     except:
        ...             reraise(sys.exc_info(), "really common problem")
        ...
    """
    excClass, exc, tb = exc_info
    #LOG.info(excClass, exc, tb)
    try:
        # re-raise original exception with added custom error message
        raise excClass, excClass("%s: %s" % (exc, errorMsg)), tb
    except TypeError:
        if excClass == TypeError:
            # original exception is TypeError, which has a standard constructor:
            # safe to re-raise this way
            raise excClass, excClass("%s: %s" % (exc, errorMsg)), tb

        # TypeError due to non-standard exception constructor
        if isinstance(excClass, (types.ClassType, type)) \
           and issubclass(excClass, Exception):
            # raise derived exception class with added custom error message
            class CustomInfoException(excClass):
                def __init__(self, info=''):
                    self._info = info

                def __str__(self):
                    return "%s" % (self._info)

            CustomInfoException.__name__ = excClass.__name__
            raise CustomInfoException, \
                  CustomInfoException(info='%s, %s'%(exc.args, errorMsg)), tb
        else:
            # raise base Exception class with added original exception
            # message plus custom error message. Safe for old string exceptions.
            raise Exception, \
                  Exception("%s: %s: %s"
                            % (getattr(excClass, '__name__', excClass), exc or
                               excClass, errorMsg)), tb


def filename():
    return inspect.currentframe().f_code.co_filename

def lineno():
    """Return the current line number in our program"""
    return inspect.currentframe().f_back.f_lineno


def get_utf8_value(value):
    if not isinstance(value, str) and not isinstance(value, unicode):
        value = str(value)
    if isinstance(value, unicode):
        return value.encode('utf-8')
    else:
        return value


yy = lambda x, y: math.ceil(x/float('%.f'%(y))) if x is not None else 0


def conv_float(x, decimal=2):
    #只保留2位，不进位
    assert(isinstance(x, float))
    a, b = str(x).split('.')
    return float(a + '.' + b[:decimal])


def retains_decimal(val, decimal=0):
    prefix = "%.f"
    if decimal > 0:
        prefix = "%.%sf" % decimal
    val = prefix % val
    return val


def percent(dividend, divisor, default=100, decimal=0):
    '''
        2/0 = default
        None/1 = default
        result type float
    '''
    quotient = default
    if not dividend or not divisor:
        return default
    if int(divisor) != 0:
        quotient = float(dividend) / float(divisor) * 100
    quotient = retains_decimal(quotient)
    return quotient


def package_error(recode, error, cmd=''):
    if not error:
        return

    if 'not support' in error and 'cache' in error:
        raise Exception(u"磁盘不支持缓存，加入集群失败")
 
    if 'lichd not running' in error:
        raise Exception(u"存储服务未运行")

    if "File exists" in error and ('create' in cmd or 'clone' in cmd):
        raise Exception(u"指定的名称已被使用")

    if 'Required key not available' in error and ('snapshot --rollback' in cmd or 'snapshot --clone' in cmd):
        raise Exception(u"快照已不存在,操作失败")

    if "need recovery first" in error :
        raise Exception(u"操作失败，数据重建完成后再添加主机")
        
    if "Channel closed" in error:
        raise Exception("远程执行命令出错，协议通道异常关闭,请重试解决")
    if 'Device or resource busy'  in error and 'scan' in cmd:
        raise LichFault('数据恢复操作正在进行，请恢复完成后重试')

    if recode == 11 or "Resource temporarily unavailable" in error:
        raise LichFault('资源暂不可用，请稍后重试')
    if 'Failed to open' in error:
        part = re.compile(r'\/dev\/[a-zA-Z0-9\/]+').findall(error)
        part = ",".join(set(part))
        raise LichFault('SSD磁盘分区%s不可用'%part)

    if "Connection refused"  in error:
        raise LichFault('主机连接失败,网络不通或是关机状态')
    if 'can not add'  in error and 'system is'  in error:
        new_node_os = re.compile(r'add ([\s\S]*) node').findall(error)
        new_node_os = ",".join(set(new_node_os))
        cluster_os = re.compile(r'system is ([\s\S]*)').findall(error)
        cluster_os = ",".join(set(cluster_os))
        raise LichFault('操作系统不一致,%s操作系统的主机不可加入%s操作系统的集群中'%(new_node_os.strip(),cluster_os.strip()))

    if "command execute time out"  in error:
        raise LichFault('命令行超时退出')
    if "no space left on device" in error.lower():
        raise LichFault('操作失败，磁盘空间已满')

    if "already used by lich" in error.lower():
        raise LichFault('磁盘已经加入集群，请勿重复操作')

    if '/var/run/fusionstack_disk' in error and ".lock" in error\
            or '/var/run/fusionstack_raid_hpacucli.lock' in error \
            or '/var/run/deleting.lock' in error\
                or '/var/run/add_disk.lock' in error:
        raise LichFault('操作失败，进程处于锁定中，请稍后重试')

    if "Problem opening" in error:
        part = re.compile(r'\/dev\/[a-zA-Z0-9\/]+').findall(error)
        part = ",".join(set(part))
        raise LichFault('磁盘%s不可用，可检查是否已不存在'%part)

    if "already exist" in error:
        regep = re.compile(r'(?<![\.\d])(?:\d{1,3}\.){3}\d{1,3}')
        node = ",".join(set(regep.findall(error)))
        raise LichFault('添加的主机%s已经在集群内'%node)
    
    if "need reboot" in error:
        raise LichFault('操作失败，服务进程处于不可中断状态，需重启主机解决')
    
    if "addnode" in error and "File exists" in error:
        regep = re.compile(r'(?<![\.\d])(?:\d{1,3}\.){3}\d{1,3}/\d*')
        instence = ",".join(set(regep.findall(error)))
        raise LichFault('添加磁盘服务%s失败，服务已存在'%instence)
    if "has not subdir" in error:
        regep = re.compile(r'(?<![\.\d])(?:\d{1,3}\.){3}\d{1,3}')
        instence = ",".join(set(regep.findall(error)))
        raise LichFault('主机%s未挂载数据盘'%instence)
    if "part size must be greater than" in error:
        raise LichFault('SSD磁盘分区容量不可小于10G,可减少分区个数解决')
    if 'not a new disk' in error:
        raise LichFault('磁盘不是新盘，不能加入RAID')
    if "Operation not permitted" in error and  "mkfs.ext" in error:
        raise LichFault('磁盘格式化失败，操作不允许')
    if "Operation not permitted" in error:
        raise LichFault('操作不允许')
    if 'partitions not enough' in error:
        raise LichFault('磁盘分区不足，请选择预分区个数重新分区')
    if 'need reformat' in error:
        raise LichFault('磁盘分区不均匀，请指定分区个数重新分区')
    if "'dmsetup' version is lower than 4.27.0" in error:
        raise LichFault('dmsetup版本不可低于4.27.0')
    if 'Connection timed out' in error:
        raise LichFault('命令行执行失败,网络连接超时')
    if 'Function not implemented' in error:
        raise LichFault('命令行执行失败,功能函数没有完成准备')
    if 'failed: Device or resource busy' in error or 'device is busy' in error:
        raise LichFault('设备忙，请稍后重试')
    if 'Machine is not on the network' in error:
        raise LichFault('主机存储服务异常')
    if 'Invalid argument' in error:
        raise LichFault('无效操作')
    if "Authentication failed" in error or '密码错误，认证失败' in error:
        raise LichFault('密码错误，认证失败')
    if "no route to host" in error.lower() or "network is unreachable" in error.lower():
        raise LichFault('主机连接失败,网络不通或是关机状态')
    if "the cluster was just beginning, please try again after ten minutes" in error.lower():
        raise LichFault('集群正在重构，请十分钟后再进行该操作')

    if "time is not synchronized" in error.lower() :
        raise LichFault('添加主机时间要与集群主机时间同步，请调整该主机时间后重试')
    if "can not delete lich already used device" in error.lower():
        raise LichFault('集群数据磁盘不允许执行此操作')
    if "do not belong to storage network segment" in error.lower():
        raise LichFault('非存储网段IP不能加入集群')
    if "hostname" in error.lower() and "double" in error.lower():
        raise LichFault('添加的主机的主机名和集群配置文件中的主机名重复')
    if "this node can not add disk" in error.lower():
        raise LichFault('主机服务已全部停止,无法加入集群')

    if 'Virtual Drive' in error:
        raise LichFault("主机内存在RAID信息不正确的磁盘，请联系RAID卡供应商解决")
    if "invalid" in error and "GPT header" in error \
            or "Main and backup partition tables differ" in error:
        raise LichFault('磁盘的GPT header信息损坏,可对磁盘重新格式化恢复')
    if "partition table CRC mismatch" in error:
        raise LichFault('磁盘的分区表信息损坏,可对磁盘重新格式化恢复')
    if "has data" in error.lower() or "have data" in error.lower():
        raise LichFault('添加的主机的数据盘不为空，无法添加')
    if "can not set disk" in error.lower():
        raise LichFault('非RAID卡磁盘，不支持磁盘灯闪烁')
    if "not a block device" in error.lower() and "umount" not in error.lower():
        raise LichFault('操作对象不是一个块设备')
    if "is not valid device" in error:
        start = "ERROR:"
        end = "is not valid device"
        obj = error[start+6:end].strip()
        if not obj:
            obj = "操作对象"
        if 'cache' in obj:
            reobj = re.compile(r'cachedev\d+').findall(obj)
            obj = ",".join(set(reobj))
            obj = "/dev/mapper/%s"%obj
        raise LichFault('%s不是一个可用设备'%obj)
    if "not a block device" in error.lower() and "can not umount" in error.lower():
        raise LichFault('取消挂载磁盘失败')

    if "Permission denied" in error.lower() and "Invalid license" in error.lower():
        raise LichFault('集群注册信息已失效，请更新注册信息后进行该操作')

    if 'strange behavior now' in error.lower() and 'fc_format' in cmd:
        raise LichFault('磁盘可能已发生改变')

    if 'strange behavior now' in error.lower() and \
            ('raid_add' in cmd or 'raid_del' in cmd ):
        raise Exception("操作失败，检查磁盘是否已损坏")

    index = error.find('key is not equal') 
    if index != -1:
        ip = error[index+20:]
        raise LichFault('管理系统校验集群的管理权限失败，集群已经被(%s)管理'%ip)

    if 'directory not empty' in error.lower():
        raise Exception("目录不为空")


def inspect_func(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        #print '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<', func.__module__, func.__name__
        #print '--- ARG=', args, kw
        res = func(*args, **kw)
        #print '--- RES='
        #pprint(res)
        #print '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', func.__module__, func.__name__
        return res
    return wrapper


if __name__ == '__main__':
    LOG.info(__file__)
    LOG.info(__name__)
    LOG.info(filename(), lineno())
    try:
        try:
            #raise Exception('sy')
            a = 1
            a + 'a'
        except Exception:
            reraise(sys.exc_info(), 'i go it, and throw')
    except Exception, e:
        LOG.info(e)
